AWS Client VPN のコンポーネントを絵に描いて理解しつつ NetworkACL や SecuriryGroup をどこまで絞れるのか試してみた
コンバンハ、千葉(幸)です。
AWS Client VPN は、VPC 上の AWS リソース、および VPC を経由した対向の環境への安全なアクセスを提供する マネージドな クライアントベースの VPN サービスです。
以下のイメージのように、Client VPN のエンドポイントを VPC に関連付けることで、各種環境へのアクセスが可能になります。
AWS Client VPN の仕組み - AWS Client VPNより
ユースケースのひとつとして、いわゆる踏み台サーバの代替として使用する、というものがあります。プライベートサブネット上のサーバに対してアクセスしたい場合、パブリックサブネットに踏み台を建てそこを経由する、というのは従来からよくある手法です。
踏み台サーバの代わりに Client VPN を使用し、クライアントからプライベートサブネット上のサーバに対してアクセスすることが可能です。踏み台サーバの維持管理を行う必要が無くなったり、幅広い認証の方式に対応しているといったメリットがあります。
ここで、Client VPN を VPC に関連づけた場合、VPC のネットワーク設計にどのような影響を及ぼすでしょうか。
Client VPN におけるコンポーネントでどういったものがあるかという点や、SecuriryGroup 、 NetworkACL 、ルートテーブルなど既存の VPC 環境に手を加えないといけない部分が気になります。
今回は、SecuriryGroup や NetworkACL を最小限まで絞ってみることで、どういった通信が発生するかについて理解を深めていきます。
目次
先に結論のようなもの
もりもりで表すと以下のようなイメージとなります。なお、今回は以下の前提で構成を考えています。
- クライアントがアクセス要件を持つのは VPC 内のリソースのみ
- 認証は相互認証(証明書ベース)で行う
- スプリットトンネルを有効化する
- AWS Client VPN エンドポイントを VPC に関連づける際は、ターゲットネットワークとしてサブネットを指定する
- ターゲットネットワークには、エンドポイントと紐づく ENI が生成される
- ターゲットネットワーク上の ENI は、クライアント VPN エンドポイントからのトラフィックの送信元 IP を NAT する
- クライアントからターゲットネットワーク上の ENI に至るまでの通信に関して SecuriryGroup や NetworkACL の影響は考慮不要である
- ターゲットネットワーク 上の ENI と、アクセス先の サーバ間の通信を考慮すればよい
AWS Client VPN のコンポーネント
先に主要なコンポーネントについて抑えておきましょう。
AWS Client VPN とは - AWS Client VPN
ターゲットネットワーク
上述したとおり、AWS Client VPN エンドポイントを VPC に関連づける際は、サブネットをターゲットネットワークとして指定する必要があります。
ターゲットネットワークとして指定できるのはアベイラビリティゾーンごとに一つのサブネットまでで、少なくとも /27 ビットマスクを持つ CIDR ブロックが必要です。また、最低 8 個の IP アドレスの空きがある必要があります。
Client VPN エンドポイントを作成した時点では料金はかからず、ターゲットネットワークに関連づけた時点で課金対象となります。
クライアント CIDR
クライアントに割り当てられる IP レンジです。ブロックサイズが /22 以上、/12 以下である必要があります。
クライアントと Client VPN エンドポイント間では、初回にグローバル IP による接続が行われたのち、トンネリングによってプライベートIP アドレスでの通信が可能になります。ここでクライアントが使用するプライベート IP アドレスは、このクライアント CIDR にプールされたものから払い出されることになります。
混乱しやすい部分ですが、アクセス先のサーバから見た送信元 IP はこのクライアント CIDR の IP アドレスではありません。ターゲットネットワーク 上の ENI によって送信元 IP アドレスの NAT が行われるため、アクセス先サーバから見た送信元 IP はターゲットネットワーク上の ENI が持つそれとなります。
承認ルール
承認ルールは、Client VPN を介してアクセスできるネットワーク、およびユーザーを制御するためのファイアウォールとして機能するコンポーネントです。
Client VPN エンドポイントを経由して接続可能なネットワークは、ここで定義しておく必要があります。例えば VPC 経由でインターネットに接続可能としたい場合には、ここで 0.0.0.0/0 を許可しておく必要があります。 VPC より小さい範囲でネットワークを指定することもできます。
また、AWS Clietn VPN においては大まかに以下の 3 種の認証方式があります。
- 相互認証(証明書ベース)
- Active Directory 認証(ユーザーベース)
- SAML ベースのフェデレーション認証(ユーザーベース)
ユーザーベースの認証方式を用いる場合は、承認ルールにおけるユーザーの制御を行うことができます。Active Directory グループの SID 、SAML ベースの Idp で定義されたグループ ID/名前 の単位で、各ユーザーが接続可能なネットワークをコントロールできます。
相互認証の場合や、ユーザー単位での制御を行わない場合は、「すべてのユーザー」という単位で設定することになります。
先ほどのネットワークの定義と組み合わせて、「特定のユーザーはこのネットワークにアクセスできる」という制御が実現できます。
Client VPN ルートテーブル
サブネットルートテーブルとは別に、Client VPN に紐付けるルートテーブルが存在します。
ルートテーブル内のエントリとして、以下の組み合わせを定義する必要があります。
- 宛先の CIDR
- ターゲットネットワーク
Client VPN エンドポイントを サブネットに関連づけた場合、デフォルトで宛先を VPC としてルートが追加されます。VPC 外へのアクセスを行いたい場合には、そこに向けたルートを追加する必要があります。
注意点として、複数のターゲットネットワークを関連づけた場合、ルートテーブル内のエントリは両者で揃える必要があります。
上記のイメージですと、ターゲットネットワーク C に割り振られた場合、0.0.0.0/0
に向けたエントリが存在しないため、クライアントが VPC 経由でインターネットにアクセスしようとした場合にうまく繋がらないという事象が発生します。
ターゲットネットワーク 内の ENI が持つプライベート IP に対応する グローバル IP が存在し、クライアントが Client VPN エンドポイントに接続する際のグローバル IP は DNS ラウンドロビンアルゴリズムによって決定されます。
クライアントはどちらのターゲットネットワークを経由するかを選択できないため、ルートテーブルのエントリは揃えておく必要があるということです。
クライアント VPN のトラブルシューティング - AWS Client VPN
スプリットトンネル
VPN セッションを接続したのち、デフォルトではクライアントからのトラフィックはすべて Client VPN トンネルを経由して行われることになります。スプリットトンネルを有効化すると、一部のトラフィック以外は、トンネルを経由せず通常通り(VPN セッションを開始する前の状態)のルートで通信が行われます。
「一部」に該当するのは、「Client VPN ルートテーブルで宛先として定義されている CIDR 向けのトラフィック」です。
例えば、Client VPN ルートテーブルにおいてデフォルトの「VPC の CIDR 宛」のみが定義されていた場合、クライアントがインターネットに接続する際には Client VPN トンネルを経由せず、直接インターネットに出ていくこととなります。
上記の例に相当する内容を実際に試しているエントリが以下となりますので、あわせてご参照ください。
VPC 上のネットワーク要素への影響
AWS Client VPN におけるコンポーネントが一通り理解できたところで、導入した際に既存のネットワーク要素にどのような影響を及ぼすかを考えてみましょう。
なお、今回は以下のケースを前提に考えていきます。
- クライアントからプライベートサブネット上のインスタンスに SSH 接続を行いたい
- VPC 外の環境へのアクセス要件はない
- 認証は相互認証(証明書ベース)で行う
- スプリットトンネルを有効化する
クライアントから Client VPN を経由し宛先のインスタンスにアクセスするまでの経路における、送信元および宛先の IP アドレス/ポートを整理すると以下のようになります。戻りの通信においては、送信元と宛先を入れ替えればよいです。
VPC 内部の通信としては、ターゲットネットワークと宛先のネットワーク間の通信だけを考慮すれば問題ありません。
それを踏まえて ルートテーブル、SecuriryGroup、Network ACL で最低限許可しなければならないものを考えると、以下のようになります。
ルートテーブル
ルートテーブルにはデフォルトで以下のルートが登録されています。
- 送信先:VPC CIDR
- ターゲット: local
今回考慮すべきは同一 VPC 内の通信であるため、特に追加の設定は必要ありません。もし Client VPN を経由して VPC 外の環境に通信を行いたい場合は、ターゲットネットワークのサブネットルートテーブルにルートを追加する必要があります。
SecuriryGroup
ターゲットネットワークの ENI と、宛先の EC2 インスタンスにそれぞれ異なる SecuriryGroup をアタッチするものとします。
SecuriryGroup では送信元や宛先として CIDR だけでなく SecuriryGroup を指定することもできる(相互参照)ため、その機能を用いてお互いに必要な通信を許可します。
さらに細かく絞ろうと思えば各コンポーネントの IP を/32 単位で指定することもできますが、そこまで行くとやりすぎ感があるのでやめておきます。
Network ACL
NetworkACL は SecuriryGroup と異なりステートレスであるため、戻りの通信を許可する必要があります。また、ルール番号の若い順番に評価されるという違いもあります。
デフォルトで「すべてを拒否する」というルールが最後尾に設定されるため、それより優先的に評価されるルールを追加する必要があります。
また、SecuriryGroup のように相互参照の機能は存在しないため、送信元/宛先 は CIDR で指定する必要があります。
ここでもやろうと思えば /32 で絞ることもできますが、現実的な落とし所として対向のネットワークのサブネットの CIDR 単位で絞るようにしました。
SecuriryGroup と Network ACL の使い分けについては以下エントリに詳しいですのであわせてご参照ください。
やってみた
ここまで長々と理屈を語ってきましたが、実際にやってみましょう。
今回は以下の構成で試してみました。
Client VPN のルートテーブルはデフォルトのままで、承認ルールは 192.168.1.0/24
をすべてのユーザーに許可しています。
Client VPN の状態
Clietn VPN のセットアップの手順は割愛します。これからのセットアップされる方は、以下のエントリを参考にしてください。
概要はこのような形になっています。
DNS 名が払い出されており、クライアントが VPN セッションを開始する際にはこれを名前解決して得られるグローバル IP を宛先とすることになります。(クライアント設定のコンフィグ内で定義されています。)
ホスト名として任意のものが設定できるので、実際に適当なものを付与して名前解決してみると、2 つのグローバル IP が返却されました。
% dig +noall +ans chiba.cvpn-endpoint-0baxxxxxxxxxxc60d.prod.clientvpn.ap-northeast-1.amazonaws.com chiba.cvpn-endpoint-0baxxxxxxxxxxc60d.prod.clientvpn.ap-northeast-1.amazonaws.com. 10 IN A 18.xxx.9.213 chiba.cvpn-endpoint-0baxxxxxxxxxxc60d.prod.clientvpn.ap-northeast-1.amazonaws.com. 10 IN A 54.xxx.7.36
ターゲットネットワークには ENI が 2 つ作成されています。プライベート IP と、それに紐づくグローバル IP が確認できます。ここで確認できるグローバル IP は上で確認したものとは異なっています。
こちらは飽くまで、ターゲットネットワークの ENI がインターネットに接続する際に用いられるもののようです。
Elastic IP が割り当てられていますが、自身が所有するものではありません。よって、コンソールの EIP の画面から確認できませんし、サービスクォータ(上限)に対して使用数を消費するものでもありません。 Client VPN サービスとして確保されているものかと思います。
クライアントからの接続
クライアントのネットワーク環境は以下のようになっています。
ダウンロード済みの AWS VPN Client で接続を実行します。
クライアントのルーティングテーブルに以下ハイライト部が追加されました。
% netstat -rn Routing tables Internet: Destination Gateway Flags Netif Expire default 192.168.0.1 UGSc en0 127 127.0.0.1 UCS lo0 127.0.0.1 127.0.0.1 UH lo0 169.254 link#6 UCS en0 ! 172.31/27 172.31.0.2 UGSc utun2 172.31.0.2 172.31.0.2 UH utun2 192.168.0 link#6 UCS en0 ! 192.168.0/16 172.31.0.1 UGSc utun2 192.168.0.1/32 link#6 UCS en0 ! 192.168.0.1 90:f3:5:ee:a2:bb UHLWIir en0 1156 192.168.0.14/32 link#6 UCS en0 ! 192.168.0.14 a4:83:e7:60:89:26 UHLWI lo0 224.0.0/4 link#6 UmCS en0 ! 224.0.0.251 1:0:5e:0:0:fb UHmLWI en0 239.255.255.250 1:0:5e:7f:ff:fa UHmLWI en0 255.255.255.255/32 link#6 UCS en0 !
この状態で アクセス先のサーバのプライベート IP を指定して SSH 接続を試みると、問題なく成功します。
% ssh [email protected] -i ~/.ssh/hogehoge.pem Last login: Fri Aug 7 18:08:11 2020 from ip-192-168-10-97.ap-northeast-1.compute.internal __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/ [ec2-user@ip-192-168-1-63 ~]$
SSH の接続は、ターゲットネットワーク ENI が送信元 として行われていることが確認できます。
[ec2-user@ip-192-168-1-63 ~]$ sudo netstat -anp Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 3347/sshd tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 3147/master tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 2678/rpcbind tcp 0 268 192.168.1.63:22 192.168.10.97:57834 ESTABLISHED 7622/sshd: ec2-user tcp6 0 0 :::22 :::* LISTEN 3347/sshd tcp6 0 0 :::111 :::* LISTEN 2678/rpcbind udp 0 0 0.0.0.0:68 0.0.0.0:* 2901/dhclient udp 0 0 0.0.0.0:111 0.0.0.0:* 2678/rpcbind udp 0 0 0.0.0.0:728 0.0.0.0:* 2678/rpcbind udp 0 0 127.0.0.1:323 0.0.0.0:* 2703/chronyd udp6 0 0 fe80::4c8:4cff:feb8:546 :::* 3006/dhclient udp6 0 0 :::111 :::* 2678/rpcbind udp6 0 0 :::728 :::* 2678/rpcbind udp6 0 0 ::1:323 :::* 2703/chronyd
SecuriryGroup や NetworkACL をできる限り絞った形でも、問題なく通信が行えることが確認できました!
終わりに
というわけで、冒頭の絵を再掲します。
ターゲットネットワーク 内の ENI について、カスタマーはあくまで VPC 内部の通信についてのみ考慮すればよいというのがなかなか面白いですね。このあたりは実際に触ってみるまでなかなか理解が追いつかなかったです。
逆に言えば、クライアントを送信元 IP で制限できないということでもあります。SecuriryGroup に関する考え方については以下もあわせてご参照ください。
以上、検証の時にサブネットを間違えて 1 時間くらい溶かした千葉(幸)がお送りしました。